import * as THREE from "three";

// 引入轨道控制器控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 引入动画库
import gsap from "gsap";
// 引入数据图形用户界面库
import * as dat from "dat.gui";
// 引入rgbeLoad
// DataTextureLoader
// 用于加载二进制文件格式的(rgbe, hdr, ...)的抽象类。 内部使用FileLoader来加载文件， 和创建一个新的 DataTexture.
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
// 引入物理引擎
import * as CANNON from 'cannon-es';
// 导入顶点着色器与片元着色器
import WaterVertexShader from "../shader/water/WaterVertexShader.glsl";
import WaterFragmentShader from "../shader/water/WaterFragmentShader.glsl";

// 用于载入glTF 2.0资源的加载器。
// glTF（gl传输格式）是一种开放格式的规范 （open format specification）， 用于更高效地传输、加载3D内容。
// 该类文件以JSON（.gltf）格式或二进制（.glb）格式提供， 外部文件存储贴图（.jpg、.png）和额外的二进制数据（.bin）。
// 一个glTF组件可传输一个或多个场景， 包括网格、材质、贴图、蒙皮、骨架、变形目标、动画、灯光以及摄像机。
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"
import { getUnit } from "gsap/gsap-core";

//three.js 基本内容

// 1.创建场景  场景能够让你在什么地方、摆放什么东西来交给three.js来渲染，这是你放置物体、灯光和摄像机的地方。
const scene = new THREE.Scene();

// 2.创建相机 
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 300);

//2.1设置相机的位置 position表示对象局部位置的Vector3。默认值为(0, 0, 0)。
camera.position.set(0, 1, 2);
scene.add(camera); //添加相机到场景中

// 设置波浪参数
const params = {
    uWaresFrequency: 14,
    uScale: 0.03,
    uXzScale: 1.5,
    uNoiseFrequency: 10,
    uNoiseScale: 1.5,
    uLowColor: "#ff0000",
    uHighColor: "#ffff00",
    uXspeed: 1,
    uZspeed: 1,
    uNoiseSpeed: 1,
    uOpacity: 1,
}


const shaderMaterial = new THREE.ShaderMaterial({
    // 顶点着色器的GLSL代码。这是shader程序的实际代码。 
    // gl_Position 顶点变换要更新位置信息
    vertexShader: WaterVertexShader,
    // 片元着色器的GLSL代码。这是shader程序的实际代码。
    fragmentShader: WaterFragmentShader,
    side: THREE.DoubleSide,
    // 着色器中透明度要设置
    transparent:true,
    // .uniforms : Object
    // { "uniform1": { value: 1.0 }, "uniform2": { value: 2 } }
    // 指定要传递给shader代码的uniforms；键为uniform的名称，值(value)是如下形式：
    // { value: 1.0 }这里 value 是uniform的值。名称必须匹配 uniform 的name，和GLSL代码中的定义一样。 
    // 注意，uniforms逐帧被刷新，所以更新uniform值将立即更新GLSL代码中的相应值。
    uniforms: {
        uTime: {
            value: 0
        },
        uWaresFrequency: {
            value: params.uWaresFrequency
        },
        uScale: {
            value: params.uScale
        },
        uNoiseFrequency: {
            value: params.uNoiseFrequency
        },
        uNoiseScale: {
            value: params.uNoiseScale
        },
        uXzScale: {
            value: params.uXzScale
        },
        uLowColor: {
          value: new THREE.Color(params.uLowColor),
        },
        uHighColor: {
          value: new THREE.Color(params.uHighColor),
        },
        uXspeed: {
          value: params.uXspeed,
        },
        uZspeed: {
          value: params.uZspeed,
        },
        uNoiseSpeed: {
          value: params.uNoiseSpeed,
        },
        uOpacity: {
          value: params.uOpacity,
        }
    }
});

const gui = new dat.GUI();
gui.add(params, "uWaresFrequency").min(1).max(100).step(0.1).onChange((value) => {
    shaderMaterial.uniforms.uWaresFrequency.value = value;
})
gui.add(params, "uScale").min(0.1).max(0.2).step(0.0001).onChange((value) => {
    shaderMaterial.uniforms.uScale.value = value;
})
gui.add(params, "uNoiseFrequency").min(1).max(100).step(0.1).onChange((value) => {
    shaderMaterial.uniforms.uNoiseFrequency.value = value;
});
gui.add(params, "uNoiseScale").min(0).max(5).step(0.001).onChange((value) => {
    shaderMaterial.uniforms.uNoiseScale.value = value;
});
gui.add(params, "uXzScale").min(0).max(5).step(0.1).onChange((value) => {
    shaderMaterial.uniforms.uXzScale.value = value;
});
gui.addColor(params, "uLowColor").onFinishChange((value) => {
    shaderMaterial.uniforms.uLowColor.value = new THREE.Color(value);
});
gui.addColor(params, "uHighColor").onFinishChange((value) => {
    shaderMaterial.uniforms.uHighColor.value = new THREE.Color(value);
});
gui.add(params, "uXspeed").min(0).max(5).step(0.001).onChange((value) => {
    shaderMaterial.uniforms.uXspeed.value = value;
});
gui.add(params, "uZspeed").min(0).max(5).step(0.001).onChange((value) => {
    shaderMaterial.uniforms.uZspeed.value = value;
});
gui.add(params, "uNoiseSpeed").min(0).max(5).step(0.001).onChange((value) => {
    shaderMaterial.uniforms.uNoiseSpeed.value = value;
});
gui.add(params, "uOpacity").min(0).max(1).step(0.01).onChange((value) => {
      shaderMaterial.uniforms.uOpacity.value = value;
});


// 平面
const floor = new THREE.Mesh(
    new THREE.PlaneBufferGeometry(1, 1, 1024, 1024),
    shaderMaterial
);
floor.rotation.x = - Math.PI / 2;

scene.add(floor);


// 4.渲染
// 初始化渲染器
// WebGLRenderer WebGL Render 用WebGL渲染出你精心制作的场景。
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// .domElement : DOMElement   一个canvas，渲染器在其上绘制输出。
// 渲染器的构造函数会自动创建(如果没有传入canvas参数);你需要做的仅仅是像下面这样将它加页面里去:
document.body.appendChild(renderer.domElement); //将绘制canvas添加到页面里
// 开启场景中的阴影贴图
// renderer.shadowMap.enabled = true;
// renderer.physicallyCorrectLights = true; // 新版本不用设置 物理正确光源 decay也会生效。 老版本开阴影要设置


//创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 创建坐标轴辅助器
// AxesHelper  用于简单模拟3个坐标轴的对象.
// 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
// AxesHelper( size : Number )  size -- (可选的) 表示代表轴的线段长度. 默认为 1.
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

// 设置时钟
const clock = new THREE.Clock();


function render() {
    let elapsedTime = clock.getElapsedTime();
    shaderMaterial.uniforms.uTime.value = elapsedTime;
    controls.update();
    renderer.render(scene, camera);
    // requestAnimationFrame 是一个用于优化浏览器动画效果的 API。它可以让浏览器在下一次重绘前执行指定的回调函数，
    // 从而可以更加流畅地执行动画效果，避免了使用 setTimeout 或 setInterval 可能引起的性能问题。
    requestAnimationFrame(render);
}

render();

// 窗口变化时，更新渲染画面
window.addEventListener("resize", () => {

    // 更新摄像机视锥体长宽比
    camera.aspect = window.innerWidth / window.innerHeight;
    // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用
    camera.updateProjectionMatrix();

    // 更新渲染器
    renderer.setSize(window.innerWidth, window.innerHeight);
    // 设置渲染器像素比 通常用于避免HiDPI设备上绘图模糊
    renderer.setPixelRatio(window.devicePixelRatio);

})


